using System;
using System.Collections.Generic;
using System.Text;

namespace RapidHDL
{
    public class MicroCoreOp
    {
        //public int Code;
        public string Name;
        public int Address;
        int iBitWidth;

        public Dictionary<int, MicroCoreInstruction> Instructions = new Dictionary<int, MicroCoreInstruction>();

        MicroCoreTable oParentTable;

        public MicroCoreOp(MicroCoreTable poParentTable, string psName)
        {
            //Code = piCode;
            Name = psName;
            oParentTable = poParentTable;
        }

        private MicroCoreInstruction AddInstruction()
        {
            MicroCoreInstruction oInstruction = new MicroCoreInstruction();
            Instructions.Add(Instructions.Count, oInstruction);
            return oInstruction;
        }

        public MicroCoreInstruction AddStateInstruction(string psState, string psLabel)
        {
            MicroCoreInstruction oInstruction = AddInstruction();
            oInstruction.ControlState  = psState;
            oInstruction.Label = psLabel;
            return oInstruction;
        }

        public MicroCoreInstruction AddStateInstruction(string psState)
        {
            return AddStateInstruction(psState, "");
        }

        public MicroCoreInstruction AddJumpInstruction(string psJumpToLabel)
        {
            MicroCoreInstruction oInstruction = AddInstruction();
            oInstruction.JumpToLabel = psJumpToLabel;
            return oInstruction;
        }

        public MicroCoreInstruction AddBranchInstruction(string psStatusBit, string psJumpToLabel, string psLabel)
        {
            MicroCoreInstruction oInstruction = AddInstruction();
            oInstruction.StatusBit = psStatusBit;
            oInstruction.Label = psLabel;
            MicroCoreInstruction oJumpInstruction = AddInstruction();
            oJumpInstruction.JumpToLabel = psJumpToLabel;
            return oInstruction;
        }

        public MicroCoreInstruction AddBranchInstruction(string psStatusBit, string psJumpToLabel)
        {
            return AddBranchInstruction(psStatusBit, psJumpToLabel,"");
        }

        internal void Compile()
        {
            foreach (MicroCoreInstruction oInstruction in Instructions.Values)
            {
                oInstruction.iAddress = oParentTable.NextAddress(oInstruction.Label);
                if (oInstruction.StatusBit  != "")
                {
                    oInstruction.iStatusCode = oParentTable.StatusBitToCode(oInstruction.StatusBit);
                    oInstruction.iInstructionCode = 3;
                }
                else
                {
                    if (oInstruction.ControlState != "")
                    {
                        oInstruction.iControlCode = oParentTable.ControlStateToCode(oInstruction.ControlState);
                        oInstruction.iInstructionCode = 0;
                    }
                    else
                    {
                        oInstruction.iInstructionCode = 2; // jump
                    }
                }
            }
            if (Instructions.Count > 1)
            {
                MicroCoreInstruction oSecondLastInstruction = Instructions[Instructions.Count - 2];
                if (oSecondLastInstruction.iControlCode == 3)
                    throw new Exception("Last Instruction of Op Code Cannot Be a Branch Statement");
            }
            if (Instructions.Count == 0)
                throw new Exception("Op Code " + Name + " has no instructions defined.");
            MicroCoreInstruction oLastInstruction = Instructions[Instructions.Count - 1];
            if (oLastInstruction.iInstructionCode != 2)
                oLastInstruction.iInstructionCode = 1; // done            
            MicroCoreInstruction oFirstInstruction = Instructions[0];
            Address = oFirstInstruction.iAddress;
            
        }

        public int PostCompile()
        {
            int iLargestValue = 0;
            foreach (MicroCoreInstruction oInstruction in Instructions.Values)
            {
                if (oInstruction.iInstructionCode == 2) // jump
                {
                    oInstruction.iJumpToAddress = oParentTable.LabelToAddress(oInstruction.JumpToLabel);
                }

                if (oInstruction.iControlCode > iLargestValue)
                    iLargestValue = oInstruction.iControlCode;
                if (oInstruction.iStatusCode > iLargestValue)
                    iLargestValue = oInstruction.iStatusCode;
                if (oInstruction.iJumpToAddress > iLargestValue)
                    iLargestValue = oInstruction.iJumpToAddress;
            }
            return iLargestValue;
        }

        public void WriteROMTable(int piBitWidth)
        {
            iBitWidth = piBitWidth;
            foreach (MicroCoreInstruction oInstruction in Instructions.Values)
            {
                int iInstructionCode = oInstruction.iInstructionCode;
                int iCode = 0;
                if (iInstructionCode == 2)
                {
                    iCode = oInstruction.iJumpToAddress;
                }
                else
                {
                    if (iInstructionCode == 3)
                        iCode = oInstruction.iStatusCode;
                    else
                        iCode = oInstruction.iControlCode;
                }
                string sBinary = Conversion.IntToBinaryString(iInstructionCode, 2) + Conversion.IntToBinaryString(iCode, piBitWidth);
                int iValue = Conversion.StringBinaryToInt(sBinary);
                oParentTable.DefineROM(oInstruction.iAddress, iValue);
            }
        }

        public string ReportContents()
        {
            string sResult = "";
            string sInstruction = "";

            sResult = "[" + Name + "]";
            sResult = sResult.PadRight(15) +Address.ToString().PadRight(10) + Conversion.IntToBinaryString(Address, Conversion.MinBitWidth(oParentTable.ROM.Count - 1)).PadRight(10);
            sResult += "\n";

            foreach (MicroCoreInstruction oInstruction in Instructions.Values)
            {
                int iInstructionCode = oInstruction.iInstructionCode;
                int iCode = 0;
                string sITypeType = "";
                string sCode="";
                if (iInstructionCode == 2)
                {
                    iCode = oInstruction.iJumpToAddress;
                    sITypeType = "JMP";
                    sCode = oInstruction.JumpToLabel;
                }
                else
                {
                    if (iInstructionCode == 3)
                    {
                        iCode = oInstruction.iStatusCode;
                        sITypeType = "CMP";
                        sCode = oInstruction.StatusBit;
                    }
                    else
                    {
                        if (iInstructionCode == 0)
                            sITypeType = "CTL ";
                        else
                            sITypeType = "CTL*";
                        iCode = oInstruction.iControlCode;
                        sCode = oInstruction.ControlState;
                        
                    }
                }
                string sBinary = Conversion.IntToBinaryString(iCode, iBitWidth);
                
                if (oInstruction.Label != "")
                    sResult += oInstruction.Label + ": {" + oInstruction.iAddress.ToString() + "}\n";
                sInstruction = "";
                sInstruction += oInstruction.iAddress.ToString().PadRight(10);
                sInstruction += Conversion.IntToBinaryString(oInstruction.iAddress,Conversion.MinBitWidth(oParentTable.ROM.Count - 1)).PadRight(10);
                sInstruction += sITypeType.PadRight(10);
                sInstruction += sCode.PadRight(20);
                sInstruction += iCode.ToString().PadRight(10);
                sInstruction += sBinary.PadRight(20);
                sResult += sInstruction + "\n";
            }
            return sResult;
        }
    }
}
